package com.inibb.app.dao.sqlt;

import java.io.File;
import java.net.MalformedURLException;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Map.Entry;

import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.lang.StringUtils;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.RuntimeSingleton;
import org.apache.velocity.runtime.parser.ParseException;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.inibb.app.exception.SqlTemplateInitException;
import com.inibb.app.exception.SqlTemplateParseException;
import com.inibb.app.test.FileTest;
import com.inibb.app.util.SLF4JLogChute;
import com.inibb.app.util.VelocityEngineUtils;
import com.inibb.app.util.WildCardFilter;

public class SqlTemplateEngine {

	private static Map<String, String> jpqlCache;
	private static Map<String, String> sqlCache;
	private static final Logger logger = LoggerFactory.getLogger(SqlTemplateEngine.class);

	private static boolean debugMode = false;

	private static Object mutex = new Object();

	private static SqlTemplateEngine instance;

	public Map<String, String> getJpqlcache() {
		return jpqlCache;
	}

	public Map<String, String> getSqlcache() {
		return sqlCache;
	}

	private SqlTemplateEngine() {
		super();
	}

	public static SqlTemplateEngine getInstance() throws SqlTemplateInitException {
		
		String classPath = SqlTemplateEngine.class.getClassLoader().getResource("").getPath();

		return getInstance(classPath + "sqlTemplates/");
	}

	public synchronized static SqlTemplateEngine getInstance(String sqltFolder) throws SqlTemplateInitException {
		synchronized (mutex) {
			if (instance != null) {
				//if (logger.isTraceEnabled()) {
					//logger.trace("returning singleton SqlTemplateEngine.");
				//}
				return instance;
			} else {

				logger.info("Initiating SqlTemplateEngine.");
				instance = new SqlTemplateEngine();
				File dir = new File(sqltFolder);
				logger.debug("SQL template folder:{}", sqltFolder);
				if (dir.isDirectory()) {
					File[] files = dir.listFiles(new WildCardFilter("sqlt-*.xml"));

					if (debugMode) {
						logger.debug("getting sqlt in debug mode.");

						Map m1 = new LinkedHashMap<String, String>(1);
						Map m2 = new LinkedHashMap<String, String>(1);
						jpqlCache = LazyMap.decorate(m1, new SqltTransformer(files, "jpqls"));
						sqlCache = LazyMap.decorate(m2, new SqltTransformer(files, "sqls"));

					} else {
						logger.debug("getting sqlt in production mode.");
						jpqlCache = new HashMap<String, String>();
						sqlCache = new HashMap<String, String>();

						for (File file : files) {

							if (file.getName().endsWith(".xml") && file.getName().startsWith("sqlt-")) {
								logger.info("Resolving sqlt xml file {}", file.getName());
								try {
									Document tdoc = instance.read(file);
									instance.cacheTsqls(tdoc);
								} catch (MalformedURLException e) {
									throw new SqlTemplateInitException("SQL template initiation error!", e);
								} catch (DocumentException e) {
									throw new SqlTemplateInitException("SQL template initiation error!", e);
								}
							} else {
								logger.info("File {} will be ignored by sqlt-engine, please make sure sqlt file starts with 'sqlt-' and end with '.xml'.", file.getName());
							}
						}
					}
				} else {
					throw new SqlTemplateInitException("SQL template folder not found!");
				}

				logger.info("SqlTemplateEngine initialization completed.");

				return instance;
			}

		}

	}

	public static void initVelocity() {

		if(!RuntimeSingleton.isInitialized()){
			synchronized(mutex){
				try {
					RuntimeSingleton.setProperty(RuntimeConstants.INPUT_ENCODING, "UTF-8");
					RuntimeSingleton.setProperty(RuntimeConstants.OUTPUT_ENCODING, "UTF-8");
					RuntimeSingleton.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS, SLF4JLogChute.class.getName());
					RuntimeSingleton.setProperty("runtime.log.logsystem.slf4j.logger", SqlTemplateEngine.class.getName());
					RuntimeSingleton.init();
				} catch (Exception e) {
					logger.error("Initialize Velocity Failed.", e);
				}
			}
		}
	}

	public static VelocityEngine getVelocityEngine() {
		VelocityEngine engine = new VelocityEngine();
		/* first, we init the runtime engine. Defaults are fine. */

		Properties p = new Properties();
		// setting input and output encoding.
		p.setProperty(RuntimeConstants.INPUT_ENCODING, "UTF-8");
		p.setProperty(RuntimeConstants.OUTPUT_ENCODING, "UTF-8");
		p.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS, SLF4JLogChute.class.getName());
		p.setProperty("runtime.log.logsystem.slf4j.logger", SqlTemplateEngine.class.getName());

		// p.setProperty("userdirective",
		// "org.apache.velocity.tools.generic.directive.Ifnull");
		// 这里加载classpath里的模板
		// p.setProperty("resource.loader", "class");
		// p.setProperty("file.resource.loader.class",
		// "org.apache.velocity.runtime.resource.loader.FileResourceLoader ");
		// p.setProperty("runtime.log.logsystem.class",
		// "org.apache.velocity.runtime.log.SystemLogChute");
		// 也可以用下面方法指定一个绝对路径，不过这样要求你所有的模板都放在该路径下，是有局限的
		// p.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, templatePath);
		try {
			engine.init(p);

		} catch (Exception e) {
			e.printStackTrace();
			logger.error("error in initiating velocity: {}", e);
		}

		return engine;

	}

	/**
	 * Read Document from a XML file.
	 * 
	 * @param file
	 * @return XML document object
	 * @throws MalformedURLException
	 * @throws DocumentException
	 */
	private Document read(File file) throws MalformedURLException, DocumentException {
		SAXReader reader = new SAXReader();
		Document document = reader.read(file);
		return document;
	}

	private void cacheTsqls(Document tdoc) {

		Element root = tdoc.getRootElement();
		String namespace = root.elementTextTrim("namespace");

		logger.info("Start resolving namespace {}", namespace);

		Element element = root.element("jpqls");
		if (element != null) {
			List<Element> jpqlElements = element.elements("sqlt");

			for (Element e : jpqlElements) {
				String key = namespace + "_" + e.attributeValue("id");
				String text = e.getText();
				logger.trace("Adding jpql into cache: {}: {}", key, text);
				jpqlCache.put(key, text);
			}

		} else {
			logger.info("No jpql found under namespace {}", namespace);
		}

		Element element2 = root.element("sqls");

		if (element2 != null) {
			List<Element> sqlElements = element2.elements("sqlt");
			for (Element e : sqlElements) {
				String key = namespace + "_" + e.attributeValue("id");
				String text = e.getText();
				logger.trace("Adding native sql into cache: {}: {}", key, text);
				sqlCache.put(key, text);
			}
		} else {
			logger.info("No native sql found under namespace {}", namespace);
		}

		logger.info("Completed resolving tsql xml {}", namespace);
	}

	public String mergeStringTemplateIntoString(String stringTemplate, Map model) throws SqlTemplateParseException {
		try {
			initVelocity();
			String sqlt = VelocityEngineUtils.mergeStringTemplateIntoString(stringTemplate, model);
			// sqlt = StringUtils.remove(sqlt, "\n");
			sqlt = StringUtils.replace(sqlt, "\n", " ");
			sqlt = StringUtils.remove(sqlt, "\t");

			sqlt = StringUtils.trimToEmpty(sqlt);
			if (logger.isTraceEnabled()) {
				logger.trace("merged sql template : {}", sqlt);
			}
			return sqlt;
		} catch (ParseException e) {
			throw new SqlTemplateParseException("Sql Template can not be parsed.", e);
		}
	}

	public String getJpql(String namespace, String sqltId, Object criteria) throws SqlTemplateParseException {
		Map<String, Object> model = new HashMap<String, Object>();
		model.put("criteria", criteria);
		model.put("util", new SqltUtil());

		String jpqlTemplate = getJpqlTemplate(namespace, sqltId);
		if (StringUtils.isBlank(jpqlTemplate)) {
			throw new SqlTemplateParseException("Sqlt-JPQL named " + sqltId + " is not found under namespace " + namespace + " .");
		}

		return mergeStringTemplateIntoString(jpqlTemplate, model);
	}

	public String getNativeSql(String namespace, String sqltId, Object criteria) throws SqlTemplateParseException {
		Map<String, Object> model = new HashMap<String, Object>();
		model.put("criteria", criteria);
		model.put("util", new SqltUtil());
		String nativeSqlTemplate = getNativeSqlTemplate(namespace, sqltId);
		if (StringUtils.isBlank(nativeSqlTemplate)) {
			throw new SqlTemplateParseException("Sqlt-SQL named " + sqltId + " is not found under namespace " + namespace + " .");
		}
		return mergeStringTemplateIntoString(nativeSqlTemplate, model);
	}

	public String getJpqlTemplate(String namespace, String sqltId) {
		if (logger.isTraceEnabled()) {
			logger.trace("getting sqlt: {}", sqltId);
		}
		Map<String, String> j = getJpqlcache();
		return j.get(namespace + "_" + sqltId);
	}

	public String getNativeSqlTemplate(String namespace, String sqltId) {
		if (logger.isTraceEnabled()) {
			logger.trace("getting sqlt: {}", sqltId);
		}
		return getSqlcache().get(namespace + "_" + sqltId);
	}

	public static final void main(String[] args) throws Exception {
//		String sqltFolder = "D:/yxin/PCCW/projects/ETMS/work/iniapp/resources/sqlTemplates";
		SqlTemplateEngine engine = SqlTemplateEngine.getInstance(/*sqltFolder*/);
//		engine = SqlTemplateEngine.getInstance(sqltFolder);
//		engine = SqlTemplateEngine.getInstance(sqltFolder);
//		engine = SqlTemplateEngine.getInstance(sqltFolder);

		for (Entry<String, String> entry : engine.getSqlcache().entrySet()) {
			String key = entry.getKey();
			String stringTemplate = entry.getValue();
			logger.trace(" {}: {}", key, stringTemplate);
			Map<String, Object> model = new HashMap<String, Object>();
			model.put("bool1", true);
			model.put("bool2", false);
			//			
			String out = engine.mergeStringTemplateIntoString(stringTemplate, model);
			//			
			logger.debug(out);
		}

	}
}
